package cmm;



import java.io.*;
import java.net.*;



public class ProxyServer implements IProxyServer {

	private ClusterManagementModule CMM;
	
	private ConnectionInformation ProxyConnInfo;
	
	private ServerSocket ProxyServerSocket;
	
	private Thread ProxyThread;
	
	private String ProxyMask;
	
	private String DefaultGatewayIP;
	
	private String NetworkConnectionName;
	
	// (in milliseconds)
	private static final int AcceptTO = 100;
	
//  -----------------------------------------------------------------	
	
	/**
	 * This creates a Proxy Server thread for the CMM.
	 * 
	 * @param CMM - A refference to the activating CMM.
	 * @param ProxyConnInfo - The network connection information of the Proxy Server. 
	 * @param mask - The ip mask of the network.
	 * @param NetworkConnName - The name of the network connection of the Proxy Server.
	 * @throws IOException
	 */
	public ProxyServer(ClusterManagementModule CMM, ConnectionInformation ProxyConnInfo, String mask, String DefaultGatewayIP, String NetworkConnName) throws IOException{

		this.CMM = CMM;
		this.ProxyConnInfo = ProxyConnInfo;
		this.ProxyMask = mask;
		this.DefaultGatewayIP = DefaultGatewayIP;
		NetworkConnectionName = NetworkConnName;

		int MaxQueueLength = 0;
		
		CMM.AddLogEntry("ProxyServer", "Info - Adding static IP " + ProxyConnInfo.getIP().getHostAddress() +
				" to the local machine for the Proxy Server.");
		// Add the Proxy Server IP address to this machine 
		try {
			Runtime.getRuntime().exec("netsh interface ip add address \"" + NetworkConnectionName + "\" " + 
					ProxyConnInfo.getIP().getHostAddress() + " " + ProxyMask + " " + DefaultGatewayIP + " 1");
		} catch (IOException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
			CMM.FatalError();
			return;
		}
		
		try {
			Thread.sleep(10*1000);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		try {
			// Open a receiving server socket
			ProxyServerSocket = new ServerSocket(ProxyConnInfo.getPort(), MaxQueueLength, ProxyConnInfo.getIP());
			// Set socket accept timeout (in milliseconds)
			ProxyServerSocket.setSoTimeout(AcceptTO);
		} catch (IOException e) {
			CMM.AddLogEntry("ProxyServer", "Error - Failed to open a listening socket");
			throw e;
		}

		CMM.AddLogEntry("ProxyServer", "Info - Proxy Server socket initialized, running Proxy Server thread.");
		System.out.println("Proxy Server socket initialized. Proxy Server Ready.");
		
		
		// Start the Proxy Server thread
		ProxyThread = new Thread(this);
		ProxyThread.start();
		CMM.AddLogEntry("ProxyServer", "Info - Proxy Server initialization completed succesfully.");
	}
	
	public void run() {
			
		while(true){
			Socket OneTimeSocket = null;
			ObjectOutputStream ClientOut = null;
			
			// Check if thread got interupt indicating it should terminate
			if(Thread.interrupted())
				// Terminate the thread 
				break;
			
			try {
				try {
					// Accept a new Client connection
					OneTimeSocket = ProxyServerSocket.accept();
				} catch (SocketTimeoutException e1){
					// Check if thread got interupt indicating it should terminate
					if(Thread.interrupted())
						// Terminate the thread 
						break;
					else
						// Continue the loop
						continue;
				}
				CMM.AddLogEntry("ProxyServer", "Info - Received a new request from a Client.");
				CMM.AddLogEntry("ProxyServer", "Info - Client IP: " + OneTimeSocket.getInetAddress());
				// Open Object Output Stream to the Client
				ClientOut = new ObjectOutputStream(OneTimeSocket.getOutputStream());
				// Request the connection information of the least busy server in the Cluster from the CMM 
				ConnectionInformation ServerInfo = CMM.FindLeastBusyServerInfo();
				// Send the connection information to the Client
				ClientOut.writeObject(ServerInfo);
				CMM.AddLogEntry("ProxyServer", "Info - Sent a Server connection information to the Client.");
				CMM.AddLogEntry("ProxyServer", "Info - Server IP: " + ServerInfo.getIP());
			} catch (IOException e) {
				// TODO remove StackTrace
				e.printStackTrace();
				CMM.AddLogEntry("ProxyServer", "Error - Failed to send Server connection information to the client.");
			} finally {
				// Close the connection to the Client
				try {
					if(ClientOut != null)
						ClientOut.close();
				} catch (IOException e) {
					// TODO remove StackTrace
					e.printStackTrace();
					CMM.AddLogEntry("ProxyServer", "Error - Failed to close output stream to the client.");
				}
				try {
					if(OneTimeSocket != null)
						OneTimeSocket.close();
				} catch (IOException e) {
					// TODO remove StackTrace
					e.printStackTrace();
					CMM.AddLogEntry("ProxyServer", "Error - Failed to close socket to the client.");
				}
			}
		}
		
	}

	public void Close() throws IOException {

		CMM.AddLogEntry("ProxyServer", "Info - Received a Close command.");
		
		// (in milliseconds)
		final long KillWaitTime = 1000;
		
		ProxyThread.interrupt();
		try {
			ProxyThread.join(KillWaitTime);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		CMM.AddLogEntry("ProxyServer", "Info - Proxy Server Thread terminated.");
		
		try {
			ProxyServerSocket.close();
		} catch (IOException e) {
			// TODO remove StackTrace
			e.printStackTrace();
			CMM.AddLogEntry("ProxyServer", "Error - Failed to close ProxyServer socket.");
			throw e;
		}
		
		// Remove Proxy IP from the Machine
		try {
			Runtime.getRuntime().exec("netsh interface ip delete address \"" + NetworkConnectionName + "\" " + 
					ProxyConnInfo.getIP().getHostAddress());
		} catch (IOException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
			CMM.FatalError();
			return;
		}
		
		CMM.AddLogEntry("ProxyServer", "Info - Proxy Server closed.");
	}

}
